package com.google.code.retrobox.editor.tracker;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Receiver;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Synthesizer;
import javax.sound.midi.Track;
import javax.sound.midi.Transmitter;

import com.google.code.retrobox.editor.tracker.model.Pattern;

public class SoundSystem implements Receiver{
	
	public static final int TICKS_PPQ = 4;

	private Synthesizer synth;
	
	private Sequencer sequencer;
	
	private Sequence sequence;
	
	private Track track = null;
	
	private MidiDevice midiIn;
	
	private MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo();
	
	private Transmitter midiInTransmitter;
	
	private int currentNote = 0;
	

	
	
	
	public boolean init(){
		try {
			synth = MidiSystem.getSynthesizer();
			synth.getChannels()[0].setMono(true);
			synth.open();
			sequence = new Sequence(Sequence.PPQ, TICKS_PPQ);
			track = sequence.createTrack();
			try {
				sequencer = MidiSystem.getSequencer();
				
				if (sequencer == null)
					throw new MidiUnavailableException();
				
				sequencer.getTransmitters().get(0).close();
				sequencer.getTransmitter().setReceiver(synth.getReceiver());
				sequencer.open();
				sequencer.setTempoInBPM(120);
				synth.getChannels()[0].setMono(true);
				sequencer.setSequence(sequence);
				sequencer.setLoopCount(999);
				sequencer.setLoopStartPoint(0);
				sequencer.setLoopEndPoint(-1);
				
				//Play beep
				playNote(60);

				
				
			} catch (Exception e) {
				e.printStackTrace();
				return false;
			}
		}catch(Exception e){
			e.printStackTrace();
			return false;
		}
		return true;
	}

	public void playNote(int note) throws InvalidMidiDataException,
			MidiUnavailableException {
		ShortMessage myMsg = new ShortMessage();
		myMsg.setMessage(ShortMessage.NOTE_ON, 0, note, 93);
		long timeStamp = -1;
		synth.getReceiver().send(myMsg, timeStamp);
	}
	
	public  Object[] getMidiInputDevices(){
		List<Object> list = new ArrayList<Object>();
	
		for (int i = 0; i < infos.length; i++) {
			list.add(infos[i].getName() + "(" + infos[i].getVendor() + ")");
		}
		return list.toArray();
	}
	
	public void enableMidiInput(final int device, final Receiver receiver){
		try {
			midiIn = MidiSystem.getMidiDevice(infos[device]);
			
			midiIn.open();
			
			midiInTransmitter = midiIn.getTransmitter();
			
			midiInTransmitter.setReceiver(receiver);
			
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	public void disableMidiInput(){
		midiInTransmitter.close();
	}
	
	
	public void generateMidiData(final Pattern pattern){
		if(track != null){
			sequence.deleteTrack(track);
		};
		track = sequence.createTrack();
		
		//add new events...
		//track.add(createProgramChange(0, 82, 0));
		for(int t=0; t < pattern.getData().length; t++){
			if(pattern.getData()[t][1].equals("999")){
				track.add(createNoteOff(0,getPrevNoteValue(t,pattern),0,(long)t));
				System.out.println("Note off:" + t );
			}else if(!pattern.getData()[t][1].equals("000")){
				track.add(createNoteOn(0,(int)Integer.valueOf(pattern.getData()[t][1]),(int)Integer.valueOf(pattern.getData()[t][2]),(long)t));				
				System.out.println("Note on:" + (int)Integer.valueOf(pattern.getData()[t][1]) + ',' + (int)Integer.valueOf(pattern.getData()[t][2]) + ',' + t );
			}
		}
		
		try {
			sequencer.setSequence(sequence);
		} catch (InvalidMidiDataException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		sequencer.setLoopCount(999);
		sequencer.setLoopStartPoint(0);
		sequencer.setLoopEndPoint(-1);

		System.out.println("sequence length:" + this.sequence.getTickLength());
		System.out.println("sequencer length:" + this.sequencer.getTickLength());

	} 
	
	private int getPrevNoteValue(final int pos,final Pattern pattern){
		int note = 0;
		int t = pos - 1;
		boolean swFound = false;
		while(t > 0 && !swFound){
			if(!pattern.getData()[t][1].equals("999") && !pattern.getData()[t][1].equals("000")){
				note = Integer.valueOf(pattern.getData()[t][1]);
				swFound = true;
			}
			t--;
		}
		return note;
	}

	MidiEvent createNoteOn(final int channel, final int note, final int velocity, final long tick) {

		ShortMessage shortMessage = new ShortMessage();
		try {
			shortMessage.setMessage(ShortMessage.NOTE_ON, channel, note,
					velocity);
		} catch (InvalidMidiDataException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return (new MidiEvent(shortMessage, tick));
	}
	
	public MidiEvent createNoteOff(final int channel, final int note, final int velocity, final long tick) {

		ShortMessage shortMessage = new ShortMessage();
		try {
			shortMessage.setMessage(ShortMessage.NOTE_OFF, channel, note,
					velocity);
		} catch (InvalidMidiDataException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return (new MidiEvent(shortMessage, tick));
	}

	public MidiEvent createProgramChange(final int channel, final int instrument, final long tick) {

		ShortMessage shortMessage = new ShortMessage();
		try {
			shortMessage.setMessage(ShortMessage.PROGRAM_CHANGE, channel,
					instrument, 0);
		} catch (InvalidMidiDataException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return (new MidiEvent(shortMessage, tick));
	}
	
	public long getSequencerPosition(){
		if(sequencer.getTickPosition() > 0){
			return sequencer.getTickPosition();
		}else{
			return 0;
		}
	}
	
	public void playSequence(){
		sequencer.setLoopEndPoint(-1);
		sequencer.start();
	}
	
	public void stopSequence(){
		sequencer.stop();
	}
	
	public boolean isSequenceRunning(){
		return sequencer.isRunning();
	}
	
	public void setSequencerPosition(long ticks){
		sequencer.setTickPosition(ticks);
	}

	@Override
	public void close() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void send(MidiMessage message, long arg1) {
		System.out.println("midi received" + message.getStatus());
	}
}
